{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4ed546c8-cbad-4721-ba6b-7d185222990e",
   "metadata": {
    "tags": []
   },
   "source": [
    "# Example 1\n",
    "\n",
    "## Source, Single-connected domain, Dirichlet problem\n",
    "\n",
    "- ### EFEM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "228a690d-545c-48b5-a2d5-6a3e40d7963b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import calfem.geometry as cfg\n",
    "import calfem.mesh as cfm\n",
    "import calfem.vis as cfv\n",
    "import numpy as np\n",
    "from toolkits import gauss_line_int, gauss_tri_int\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b47a234-100f-4115-a5ee-670e2b590fdd",
   "metadata": {},
   "source": [
    "## 1. Geometry and Meshing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1cacc716-7c70-4662-8bca-79ca9c312b4d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Info    : GMSH -> Python-module\n"
     ]
    }
   ],
   "source": [
    "g = cfg.Geometry()\n",
    "\n",
    "g.point([0.0, 0.0]) # point 0\n",
    "g.point([1.0, 0.0]) # point 1\n",
    "g.point([1.0, 1.0]) # point 2\n",
    "g.point([0.0, 1.0]) # point 3\n",
    "\n",
    "factor = 50\n",
    "dir_id_1 = 100\n",
    "dir_id_2 = 200\n",
    "dir_id_3 = 300\n",
    "dir_id_4 = 400\n",
    "\n",
    "g.spline([0, 1], el_on_curve=factor, marker=dir_id_1)\n",
    "g.spline([1, 2], el_on_curve=factor, marker=dir_id_2)\n",
    "g.spline([2, 3], el_on_curve=factor, marker=dir_id_3)\n",
    "g.spline([3, 0], el_on_curve=factor, marker=dir_id_4)\n",
    "g.structuredSurface([0, 1, 2, 3])\n",
    "\n",
    "cfv.drawGeometry(g)\n",
    "cfv.showAndWait()\n",
    "\n",
    "mesh = cfm.GmshMesh(g,return_boundary_elements=True)\n",
    "\n",
    "# Degrees of freedom per node.\n",
    "mesh.elType = 2\n",
    "# Factor that changes element sizes.\n",
    "mesh.dofsPerNode = 1     \n",
    "\n",
    "coords, edof, dofs, bdofs, elementmarkers, boundaryElements  = mesh.create()\n",
    "\n",
    "element = edof - 1\n",
    "node = coords\n",
    "\n",
    "E = len(element)\n",
    "N = len(node)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "edf91826-efaf-416e-9306-d783aa7ea2c6",
   "metadata": {},
   "source": [
    "## 2. Boundary condition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2ba4fdfb-bf49-49c4-b787-3c5e9f94e771",
   "metadata": {},
   "outputs": [],
   "source": [
    "dir_edge_1 = np.array([edge['node-number-list'] for edge in boundaryElements[dir_id_1]])-1\n",
    "dir_edge_2 = np.array([edge['node-number-list'] for edge in boundaryElements[dir_id_2]])-1\n",
    "dir_edge_3 = np.array([edge['node-number-list'] for edge in boundaryElements[dir_id_3]])-1\n",
    "dir_edge_4 = np.array([edge['node-number-list'] for edge in boundaryElements[dir_id_4]])-1\n",
    "\n",
    "# boundary edge with node in counterclock-wise order\n",
    "boundary = [edge['node-number-list'] for marker in boundaryElements for edge in boundaryElements[marker]]\n",
    "\n",
    "# def dir1(x, y):\n",
    "#     return x*(1-x)\n",
    "\n",
    "# def dir2(x, y):\n",
    "#     return -y*(1-y)\n",
    "\n",
    "# def dir3(x, y):\n",
    "#     return x*(1-x)\n",
    "\n",
    "# def dir4(x, y):\n",
    "#     return -y*(1-y)\n",
    "\n",
    "def dir1(x, y):\n",
    "    return np.zeros_like(x)\n",
    "\n",
    "def dir2(x, y):\n",
    "    return np.zeros_like(x)\n",
    "\n",
    "def dir3(x, y):\n",
    "    return np.zeros_like(x)\n",
    "\n",
    "def dir4(x, y):\n",
    "    return np.zeros_like(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5fe1912-f8d3-4895-9e96-3156355eb71c",
   "metadata": {},
   "source": [
    "## 3. Assemble flexibility matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "fb18dbeb-89fc-4566-b9bc-7627f4bef33f",
   "metadata": {},
   "outputs": [],
   "source": [
    "F = np.zeros((N,N))\n",
    "\n",
    "for i, e in enumerate(element):\n",
    "    x1,y1 = node[e][0]\n",
    "    x2,y2 = node[e][1]\n",
    "    x3,y3 = node[e][2]\n",
    "    \n",
    "    A_e = 1/2*np.linalg.det(np.c_[node[e], np.ones((3,1))])\n",
    "    \n",
    "    Phi_e = (1/(2*A_e))*np.array([[x2-x3,x3-x1,x1-x2],\n",
    "                                  [y2-y3,y3-y1,y1-y2]])\n",
    "    \n",
    "    F_e = A_e * Phi_e.T @ Phi_e\n",
    "    \n",
    "    F[np.ix_(e,e)] = F[np.ix_(e,e)] + F_e\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7a01a3f7-c7e0-40b2-aeef-6ac3d2787a52",
   "metadata": {},
   "source": [
    "## 4. Assemble kinematic vector (with source, Dirichlet problem)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c855dbeb-fe00-4b49-8f23-8514e2fd3e53",
   "metadata": {},
   "outputs": [],
   "source": [
    "du = np.zeros(N)\n",
    "\n",
    "\n",
    "# Dirichlet boundary \n",
    "for edge in dir_edge_1:\n",
    "    i, j = edge\n",
    "    L = np.linalg.norm(node[i]-node[j])\n",
    "    up = gauss_line_int(dir1, *node[i], *node[j])\n",
    "    du[i] = du[i]-up/L\n",
    "    du[j] = du[j]+up/L\n",
    "\n",
    "for edge in dir_edge_2:\n",
    "    i, j = edge\n",
    "    L = np.linalg.norm(node[i]-node[j])\n",
    "    up = gauss_line_int(dir2, *node[i], *node[j])\n",
    "    du[i] = du[i]-up/L\n",
    "    du[j] = du[j]+up/L\n",
    "\n",
    "for edge in dir_edge_3:\n",
    "    i, j = edge\n",
    "    L = np.linalg.norm(node[i]-node[j])\n",
    "    up = gauss_line_int(dir3, *node[i], *node[j])\n",
    "    du[i] = du[i]-up/L\n",
    "    du[j] = du[j]+up/L\n",
    "\n",
    "for edge in dir_edge_4:\n",
    "    i, j = edge\n",
    "    L = np.linalg.norm(node[i]-node[j])\n",
    "    up = gauss_line_int(dir4, *node[i], *node[j])\n",
    "    du[i] = du[i]-up/L\n",
    "    du[j] = du[j]+up/L\n",
    "\n",
    "# Neumann boundary without source.\n",
    "# ...\n",
    "\n",
    "\n",
    "# Source \n",
    "# Particular solution q_0\n",
    "def qx_0(x, y):\n",
    "    return x\n",
    "\n",
    "def qy_0(x, y):\n",
    "    return np.zeros_like(x)\n",
    "\n",
    "for i, e in enumerate(element):\n",
    "    x1,y1 = node[e][0]\n",
    "    x2,y2 = node[e][1]\n",
    "    x3,y3 = node[e][2]\n",
    "    \n",
    "    A_e = 1/2*np.linalg.det(np.c_[node[e], np.ones((3,1))])\n",
    "    \n",
    "    Phi_e = (1/(2*A_e))*np.array([[x2-x3,x3-x1,x1-x2],\n",
    "                                  [y2-y3,y3-y1,y1-y2]])\n",
    "    \n",
    "    int_qx = gauss_tri_int(qx_0, x1, y1, x2, y2, x3, y3, degree=2)\n",
    "    int_qy = gauss_tri_int(qy_0, x1, y1, x2, y2, x3, y3, degree=2)\n",
    "    \n",
    "    par = Phi_e.T @ np.array([int_qx, int_qy])\n",
    "    \n",
    "    du[e] = du[e] - par\n",
    "\n",
    "# Neumann boundary with source.\n",
    "# ..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5619f835-d960-4905-a43c-5b77ff0dcc40",
   "metadata": {},
   "source": [
    "## 5. Constraint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0dba5f60-fa70-4d41-ac50-72d386d3aa69",
   "metadata": {},
   "outputs": [],
   "source": [
    "FF = F[:]\n",
    "ddu = du[:]\n",
    "FF[-1,:] = 0\n",
    "FF[-1,-1] = 1\n",
    "ddu[-1] = 0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c4ffc26-a9f0-408c-80eb-723329c3b8ee",
   "metadata": {},
   "source": [
    "## 6. Solve and show"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "975aa11a-a4b7-49fb-869b-39843c1242e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "s = np.linalg.inv(FF) @ ddu"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8e4ab57-f7ab-4b05-a27f-61405d7d3079",
   "metadata": {},
   "source": [
    "## 7. Construct the flux field"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "d4a4a3c5-081a-43ff-9f2a-1603428996bf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<visvis.wibjects.colorWibjects.Colorbar object at 0x000001D6CD4724C0>\n",
      "ERROR calling '_OnAxesPositionChange':\n",
      "  File \"D:\\20220326(EFEM-Poisson)\\EFEM\\EFEM\\lib\\site-packages\\visvis\\wibjects\\colorWibjects.py\", line 622, in _OnAxesPositionChange\n",
      "    self.position.x = axes.position.width+5\n",
      "  File \"D:\\20220326(EFEM-Poisson)\\EFEM\\EFEM\\lib\\site-packages\\visvis\\core\\misc.py\", line 217, in fsetWithDraw\n",
      "    fset(self, *args)\n",
      "  File \"D:\\20220326(EFEM-Poisson)\\EFEM\\EFEM\\lib\\site-packages\\visvis\\core\\base.py\", line 1126, in fset\n",
      "    self._Update()\n",
      "  File \"D:\\20220326(EFEM-Poisson)\\EFEM\\EFEM\\lib\\site-packages\\visvis\\core\\base.py\", line 928, in _Update\n",
      "    self._CalculateInPixels()\n",
      "  File \"D:\\20220326(EFEM-Poisson)\\EFEM\\EFEM\\lib\\site-packages\\visvis\\core\\base.py\", line 997, in _CalculateInPixels\n",
      "    raise Exception(\"Can only calculate the position in pixels\"+\n",
      "Exception: Can only calculate the position in pixels if the owner has a parent!\n",
      "\n"
     ]
    }
   ],
   "source": [
    "q_h = np.zeros((E,2)) \n",
    "\n",
    "for i, e in enumerate(element):\n",
    "    x1,y1 = node[e][0]\n",
    "    x2,y2 = node[e][1]\n",
    "    x3,y3 = node[e][2]\n",
    "    \n",
    "    A_e = 1/2*np.linalg.det(np.c_[node[e], np.ones((3,1))])\n",
    "    \n",
    "    Phi_e = 1/(2*A_e)*np.array([[x2-x3,x3-x1,x1-x2],\n",
    "                                [y2-y3,y3-y1,y1-y2]])\n",
    "    \n",
    "    q_e = Phi_e @ s[e]\n",
    "    q_h[i] = q_e\n",
    "\n",
    "cfv.figure()\n",
    "\n",
    "# Draw the mesh.\n",
    "\n",
    "cfv.drawElementValues(\n",
    "    ev=q_h[:,1],\n",
    "    coords=coords,\n",
    "    edof=edof,\n",
    "    dofs_per_node=mesh.dofsPerNode,\n",
    "    el_type=mesh.elType,\n",
    "    title=\"Example 01\"\n",
    "        )\n",
    "cfv.showAndWait()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "c5d2f777-7220-42d6-8984-1ba62a28ec59",
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "SplitTabWidget.focus_changed[QWidget, QWidget].emit(): argument 1 has unexpected type 'QObject'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "File \u001b[1;32mD:\\20220326(EFEM-Poisson)\\EFEM\\EFEM\\lib\\site-packages\\pyface\\ui\\qt4\\workbench\\split_tab_widget.py:312\u001b[0m, in \u001b[0;36mSplitTabWidget._focus_changed\u001b[1;34m(self, old, new)\u001b[0m\n\u001b[0;32m    309\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[0;32m    311\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_repeat_focus_changes:\n\u001b[1;32m--> 312\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfocus_changed\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43memit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    314\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m    315\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m\n",
      "\u001b[1;31mTypeError\u001b[0m: SplitTabWidget.focus_changed[QWidget, QWidget].emit(): argument 1 has unexpected type 'QObject'"
     ]
    }
   ],
   "source": [
    "# Author: Prabhu Ramachandran <prabhu at aero dot iitb dot ac dot in>\n",
    "# Copyright (c) 2007, Enthought, Inc.\n",
    "# License: BSD style.\n",
    "\n",
    "from numpy import arange, sqrt, sin\n",
    "from tvtk.api import tvtk\n",
    "from mayavi.scripts import mayavi2\n",
    "\n",
    "# Generate the scalar values.\n",
    "x = (arange(0.1, 50.0)-25)/2.0\n",
    "y = (arange(0.1, 50.0)-25)/2.0\n",
    "r = sqrt(x[:,None]**2+y**2)\n",
    "z = 5.0*sin(r)/r  #\n",
    "\n",
    "# Make the tvtk dataset.\n",
    "# tvtk.ImageData is identical and could also be used here.\n",
    "spoints = tvtk.StructuredPoints(origin=(-12.5,-12.5,0),\n",
    "                                spacing=(0.5,0.5,1),\n",
    "                                dimensions=(50,50,1))\n",
    "# Transpose the array data due to VTK's implicit ordering. VTK assumes\n",
    "# an implicit ordering of the points: X co-ordinate increases first, Y\n",
    "# next and Z last.  We flatten it so the number of components is 1.\n",
    "spoints.point_data.scalars = z.T.flatten()\n",
    "spoints.point_data.scalars.name = 'scalar'\n",
    "\n",
    "# Uncomment the next two lines to save the dataset to a VTK XML file.\n",
    "#w = tvtk.XMLImageDataWriter(input=spoints, file_name='spoints2d.vti')\n",
    "#w.write()\n",
    "\n",
    "# Now view the data.\n",
    "@mayavi2.standalone\n",
    "def view():\n",
    "    from mayavi.sources.vtk_data_source import VTKDataSource\n",
    "    from mayavi.filters.warp_scalar import WarpScalar\n",
    "    from mayavi.filters.poly_data_normals import PolyDataNormals\n",
    "    from mayavi.modules.surface import Surface\n",
    "\n",
    "    mayavi.new_scene()\n",
    "    src = VTKDataSource(data = spoints)\n",
    "    mayavi.add_source(src)\n",
    "    mayavi.add_filter(WarpScalar())\n",
    "    mayavi.add_filter(PolyDataNormals())\n",
    "    s = Surface()\n",
    "    mayavi.add_module(s)\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    view()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "dd87c2a4-41fe-4bb4-90a9-1974f889d516",
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "SplitTabWidget.focus_changed[QWidget, QWidget].emit(): argument 1 has unexpected type 'QObject'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "File \u001b[1;32mD:\\20220326(EFEM-Poisson)\\EFEM\\EFEM\\lib\\site-packages\\pyface\\ui\\qt4\\workbench\\split_tab_widget.py:312\u001b[0m, in \u001b[0;36mSplitTabWidget._focus_changed\u001b[1;34m(self, old, new)\u001b[0m\n\u001b[0;32m    309\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[0;32m    311\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_repeat_focus_changes:\n\u001b[1;32m--> 312\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfocus_changed\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43memit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    314\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m    315\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m\n",
      "\u001b[1;31mTypeError\u001b[0m: SplitTabWidget.focus_changed[QWidget, QWidget].emit(): argument 1 has unexpected type 'QObject'"
     ]
    }
   ],
   "source": [
    "# Author: Prabhu Ramachandran <prabhu@aero.iitb.ac.in>\n",
    "# Copyright (c) 2006-2020, Enthought Inc.\n",
    "# License: BSD Style.\n",
    "\n",
    "import numpy\n",
    "\n",
    "from mayavi.scripts import mayavi2\n",
    "from tvtk.tools import mlab\n",
    "from mayavi.sources.vtk_data_source import VTKDataSource\n",
    "from mayavi.filters.warp_scalar import WarpScalar\n",
    "from mayavi.modules.outline import Outline\n",
    "from mayavi.modules.surface import Surface\n",
    "\n",
    "\n",
    "def make_data():\n",
    "    \"\"\"Make some test numpy data and create a TVTK data object from it\n",
    "    that we will visualize.\n",
    "    \"\"\"\n",
    "    def f(x, y):\n",
    "        \"\"\"Some test function.\n",
    "        \"\"\"\n",
    "        return numpy.sin(x*y)/(x*y)\n",
    "\n",
    "    x = numpy.arange(-7., 7.05, 0.1)\n",
    "    y = numpy.arange(-5., 5.05, 0.05)\n",
    "    s = mlab.SurfRegular(x, y, f)\n",
    "    return s.data\n",
    "\n",
    "def add_data(tvtk_data):\n",
    "    \"\"\"Add a TVTK data object `tvtk_data` to the mayavi pipleine.\n",
    "    \"\"\"\n",
    "    d = VTKDataSource()\n",
    "    d.data = tvtk_data\n",
    "    mayavi.add_source(d)\n",
    "\n",
    "def surf_regular():\n",
    "    \"\"\"Now visualize the data as done in mlab.\n",
    "    \"\"\"\n",
    "    w = WarpScalar()\n",
    "    mayavi.add_filter(w)\n",
    "    o = Outline()\n",
    "    s = Surface()\n",
    "    mayavi.add_module(o)\n",
    "    mayavi.add_module(s)\n",
    "\n",
    "@mayavi2.standalone\n",
    "def main():\n",
    "    mayavi.new_scene()\n",
    "    d = make_data()\n",
    "    add_data(d)\n",
    "    surf_regular()\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "4d6965f0-4713-452e-b413-7c9c8b41b6e8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Author: Gael Varoquaux <gael.varoquaux@normalesup.org>\n",
    "# Copyright (c) 2008, Enthought, Inc.\n",
    "# License: BSD Style.\n",
    "\n",
    "from mayavi import mlab\n",
    "\n",
    "# To access any VTK object, we use 'tvtk', which is a Python wrapping of\n",
    "# VTK replacing C++ setters and getters by Python properties and\n",
    "# converting numpy arrays to VTK arrays when setting data.\n",
    "from tvtk.api import tvtk\n",
    "from tvtk.common import configure_input_data\n",
    "\n",
    "v = mlab.figure()\n",
    "\n",
    "# Create a first sphere\n",
    "# The source generates data points\n",
    "sphere = tvtk.SphereSource(center=(0, 0, 0), radius=0.5)\n",
    "# The mapper converts them into position in, 3D with optionally color (if\n",
    "# scalar information is available).\n",
    "sphere_mapper = tvtk.PolyDataMapper()\n",
    "configure_input_data(sphere_mapper, sphere.output)\n",
    "sphere.update()\n",
    "\n",
    "# The Property will give the parameters of the material.\n",
    "p = tvtk.Property(opacity=0.2, color=(1, 0, 0))\n",
    "# The actor is the actually object in the scene.\n",
    "sphere_actor = tvtk.Actor(mapper=sphere_mapper, property=p)\n",
    "v.scene.add_actor(sphere_actor)\n",
    "\n",
    "# Create a second sphere\n",
    "sphere2 = tvtk.SphereSource(center=(7, 0, 1), radius=0.2)\n",
    "sphere_mapper2 = tvtk.PolyDataMapper()\n",
    "configure_input_data(sphere_mapper2, sphere2.output)\n",
    "sphere2.update()\n",
    "p = tvtk.Property(opacity=0.3, color=(1, 0, 0))\n",
    "sphere_actor2 = tvtk.Actor(mapper=sphere_mapper2, property=p)\n",
    "v.scene.add_actor(sphere_actor2)\n",
    "\n",
    "# Create a line between the two spheres\n",
    "line = tvtk.LineSource(point1=(0, 0, 0), point2=(7, 0, 1))\n",
    "line_mapper = tvtk.PolyDataMapper()\n",
    "configure_input_data(line_mapper, line.output)\n",
    "line.update()\n",
    "line_actor = tvtk.Actor(mapper=line_mapper)\n",
    "v.scene.add_actor(line_actor)\n",
    "\n",
    "# And display text\n",
    "vtext = tvtk.VectorText()\n",
    "vtext.text = 'Mayavi'\n",
    "text_mapper = tvtk.PolyDataMapper()\n",
    "configure_input_data(text_mapper, vtext.get_output())\n",
    "vtext.update()\n",
    "p2 = tvtk.Property(color=(0, 0.3, 0.3))\n",
    "text_actor = tvtk.Follower(mapper=text_mapper, property=p2)\n",
    "text_actor.position = (0, 0, 0)\n",
    "v.scene.add_actor(text_actor)\n",
    "\n",
    "# Choose a view angle, and display the figure\n",
    "mlab.view(85, -17, 15, [3.5, -0.3, -0.8])\n",
    "mlab.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c626748e-b538-445d-863c-a893dc1ddbad",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "EFEM",
   "language": "python",
   "name": "efem"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
